// This is class for matrix

// column-major 4x4 matrix
// |data[0] data[4] data[8]  data[12]|
// |data[1] data[5] data[9]  data[13]|
// |data[2] data[6] data[10] data[14]|
// |data[3] data[7] data[11] data[15]|
function gewMat4(data)
{
  if(data)
    this.data = new WebGLFloatArray(data);
  else
    this.data = new WebGLFloatArray([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]);
  //this.data = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1];
}
// Get data at row i and column j
gewMat4.prototype.get = function(i,j)
{
  return this.data[i+j*4];
}
// Set the data of this matrix
gewMat4.prototype.set = function(array)
{
  var data = this.data;
  data[0] = array[0];
  data[1] = array[1];
  data[2] = array[2];
  data[3] = array[3];
  data[4] = array[4];
  data[5] = array[5];
  data[6] = array[6];
  data[7] = array[7];
  data[8] = array[8];
  data[9] = array[9];
  data[10] = array[10];
  data[11] = array[11];
  data[12] = array[12];
  data[13] = array[13];
  data[14] = array[14];
  data[15] = array[15];
}
// Return row i as a array;
gewMat4.prototype.row = function(i)
{
  //var rowi = new WebGLFloatArray(4);
  var rowi = [];
  var data = this.data;
  rowi[0] = data[i];
  rowi[1] = data[i+4];
  rowi[2] = data[i+8];
  rowi[3] = data[i+12];
  return rowi;
}
// Return column i as a array
gewMat4.prototype.column = function(i)
{
  var coli = new WebGLFloatArray(4);
  //var coli = [];
  var data = this.data;
  var offset = i*4;
  coli[0] = data[offset];
  coli[1] = data[offset+1];
  coli[2] = data[offset+2];
  coli[3] = data[offset+3];
  return coli;
}
// Return diagonal as a array
gewMat4.prototype.diagonal = function()
{
  var data = this.data;
  var d = new WebGLFloatArray([data[0],data[5],data[10],data[15] ]);
  //d = [data[0],data[5],data[10],data[15] ];
  return d;
}

// Create a copy of this matrix
gewMat4.prototype.dup = function()
{
  return (new gewMat4(this.data));
}
gewMat4.prototype.copyFrom = function(rhs)
{
  this.set(rhs.data);
}

// Compare two Matrix
gewMat4.prototype.isEqual = function(rhs)
{
  var td = this.data;
  var rd = rhs.data;
  for(var i=0;i<16;i++)
  {
    if( Math.abs(td[i]-rd[i]) > GEW_MATH_EPSILON )
      return false;
  }
  return true;
}
// Operator + - * 
gewMat4.prototype.add = function(rhs,result)
{
  //Unrolling loop for best performance
  if(!result)
    result = new gewMat4();
  var md = result.data;
  var td = this.data;
  var rd = rhs.data;
  md[0] = td[0]+rd[0];
  md[1] = td[1]+rd[1];
  md[2] = td[2]+rd[2];
  md[3] = td[3]+rd[3];
  md[4] = td[4]+rd[4];
  md[5] = td[5]+rd[5];
  md[6] = td[6]+rd[6];
  md[7] = td[7]+rd[7];
  md[8] = td[8]+rd[8];
  md[9] = td[9]+rd[9];
  md[10] = td[10]+rd[10];
  md[11] = td[11]+rd[11];
  md[12] = td[12]+rd[12];
  md[13] = td[13]+rd[13];
  md[14] = td[14]+rd[14];
  md[15] = td[15]+rd[15];
  return result;
}
gewMat4.prototype.sub = function(rhs,result)
{
  if(!result)
    result = new gewMat4();
  var md = result.data;
  var td = this.data;
  var rd = rhs.data;
  md[0] = td[0]-rd[0];
  md[1] = td[1]-rd[1];
  md[2] = td[2]-rd[2];
  md[3] = td[3]-rd[3];
  md[4] = td[4]-rd[4];
  md[5] = td[5]-rd[5];
  md[6] = td[6]-rd[6];
  md[7] = td[7]-rd[7];
  md[8] = td[8]-rd[8];
  md[9] = td[9]-rd[9];
  md[10] = td[10]-rd[10];
  md[11] = td[11]-rd[11];
  md[12] = td[12]-rd[12];
  md[13] = td[13]-rd[13];
  md[14] = td[14]-rd[14];
  md[15] = td[15]-rd[15];
  return result;
}
gewMat4.prototype.mulScalar = function(f,result)
{
  if(!result)
    result = new gewMat4();
  var r = result.data;
  var m = this.data;
  r[0] = m[0]*f;r[1] = m[1]*f;r[2] = m[2]*f;r[3] = m[3]*f;
  r[4] = m[4]*f;r[5] = m[5]*f;r[6] = m[6]*f;r[7] = m[7]*f;
  r[8] = m[8]*f;r[9] = m[9]*f;r[10] = m[10]*f;r[11] = m[11]*f;
  r[12] = m[12]*f;r[13] = m[13]*f;r[14] = m[14]*f;r[15] = m[15]*f;
  return result;
}
gewMat4.prototype.mul = function(rhs,result)
{
  if(!result)
    result = new gewMat4();
  var r = result.data;
  var m1 = this.data;
  var m2 = rhs.data;
  m10 =  m1[0], m11 =  m1[4], m12 =  m1[8], m13 =  m1[12],
  m14 =  m1[1], m15 =  m1[5], m16 =  m1[9], m17 =  m1[13],
  m18 =  m1[2], m19 =  m1[6], m110 = m1[10],m111 = m1[14],
  m112 = m1[3], m113 = m1[7], m114 = m1[11],m115 = m1[15],
      
  m20 =  m2[0], m21 =  m2[4], m22 =  m2[8], m23 =  m2[12],
  m24 =  m2[1], m25 =  m2[5], m26 =  m2[9], m27 =  m2[13],
  m28 =  m2[2], m29 =  m2[6], m210 = m2[10],m211 = m2[14],
  m212 = m2[3],m213 = m2[7],m214 = m2[11],m215 = m2[15];
  
  r[0] = m10 * m20 + m11 * m24 + m12 * m28 + m13 * m212;
  r[1] = m14 * m20 + m15 * m24 + m16 * m28 + m17 * m212;
  r[2] =   m18 * m20 + m19 * m24 + m110 * m28 + m111 * m212;
  r[3] =   m112 * m20 + m113 * m24 + m114 * m28 + m115 * m212;
      
  r[4] =   m10 * m21 + m11 * m25 + m12 * m29 + m13 * m213;
  r[5] =   m14 * m21 + m15 * m25 + m16 * m29 + m17 * m213;
  r[6] =   m18 * m21 + m19 * m25 + m110 * m29 + m111 * m213;
  r[7] =   m112 * m21 + m113 * m25 + m114 * m29 + m115 * m213;     
    
  r[8] =   m10 * m22 + m11 * m26 + m12 * m210 + m13 * m214;
  r[9] =   m14 * m22 + m15 * m26 + m16 * m210 + m17 * m214;
  r[10] = m18 * m22 + m19 * m26 + m110 * m210 + m111 * m214;
  r[11] = m112 * m22 + m113 * m26 + m114 * m210 + m115 * m214;
    
  r[12] = m10 * m23 + m11 * m27 + m12 * m211 + m13 * m215;
  r[13] = m14 * m23 + m15 * m27 + m16 * m211 + m17 * m215;
  r[14] = m18 * m23 + m19 * m27 + m110 * m211 + m111 * m215;
  r[15] = m112 * m23 + m113 * m27 + m114 * m211 + m115 * m215;
    
  /*r[0] = m1[0]*m2[0] + m1[4]*m2[1]+ m1[8]*m2[2] + m1[12]*m2[3];
  r[1] = m1[1]*m2[0] + m1[5]*m2[1]+ m1[9]*m2[2] + m1[13]*m2[3];
  r[2] = m1[2]*m2[0] + m1[6]*m2[1]+ m1[10]*m2[2] + m1[14]*m2[3];
  r[3] = m1[3]*m2[0] + m1[7]*m2[1]+ m1[11]*m2[2] + m1[15]*m2[3];

  r[4] = m1[0]*m2[4] + m1[4]*m2[5]+ m1[8]*m2[6] + m1[12]*m2[7];
  r[5] = m1[1]*m2[4] + m1[5]*m2[5]+ m1[9]*m2[6] + m1[13]*m2[7];
  r[6] = m1[2]*m2[4] + m1[6]*m2[5]+ m1[10]*m2[6] + m1[14]*m2[7];
  r[7] = m1[3]*m2[4] + m1[7]*m2[5]+ m1[11]*m2[6] + m1[15]*m2[7];

  r[8] = m1[0]*m2[8] + m1[4]*m2[9]+ m1[8]*m2[10] + m1[12]*m2[11];
  r[9] = m1[1]*m2[8] + m1[5]*m2[9]+ m1[9]*m2[10] + m1[13]*m2[11];
  r[10] = m1[2]*m2[8] + m1[6]*m2[9]+ m1[10]*m2[10] + m1[14]*m2[11];
  r[11] = m1[3]*m2[8] + m1[7]*m2[9]+ m1[11]*m2[10] + m1[15]*m2[11];

  r[12] = m1[0]*m2[12] + m1[4]*m2[13]+ m1[8]*m2[14] + m1[12]*m2[15];
  r[13] = m1[1]*m2[12] + m1[5]*m2[13]+ m1[9]*m2[14] + m1[13]*m2[15];
  r[14] = m1[2]*m2[12] + m1[6]*m2[13]+ m1[10]*m2[14] + m1[14]*m2[15];
  r[15] = m1[3]*m2[12] + m1[7]*m2[13]+ m1[11]*m2[14] + m1[15]*m2[15];*/
  return result;
}

gewMat4.prototype.mulVec3 = function(vector,result)
{
  var data = this.data;
  var x = vector.x;
  var y = vector.y;
  var z = vector.z;
  if(!result)
    return new gewVec3(data[0]*x+data[4]*y+data[8]*z+data[12],
                       data[1]*x+data[5]*y+data[9]*z+data[13],
                       data[2]*x+data[6]*y+data[10]*z+data[14]);
  else
  {
    result.x = data[0]*x+data[4]*y+data[8]*z+data[12];
    result.y = data[1]*x+data[5]*y+data[9]*z+data[13];
    result.z = data[2]*x+data[6]*y+data[10]*z+data[14];
    return result;
  }
}

gewMat4.prototype.mulVec3NoTranslate = function(vector,result)
{
  var data = this.data;
  var x = vector.x;
  var y = vector.y;
  var z = vector.z;
  if(!result)
    return new gewVec3(data[0]*x+data[4]*y+data[8]*z,
                       data[1]*x+data[5]*y+data[9]*z,
                       data[2]*x+data[6]*y+data[10]*z);
  else
  {
    result.x = data[0]*x+data[4]*y+data[8]*z;
    result.y = data[1]*x+data[5]*y+data[9]*z;
    result.z = data[2]*x+data[6]*y+data[10]*z;
    return result;
  }
}

gewMat4.prototype.mulVec4 = function(vector,result)
{
  var data = this.data;
  var x = vector.x;
  var y = vector.y;
  var z = vector.z;
  var w = vector.w;
  if(!result)
    return new gewVec4(data[0]*x+data[4]*y+data[8]*z+data[12]*w,
                       data[1]*x+data[5]*y+data[9]*z+data[13]*w,
                       data[2]*x+data[6]*y+data[10]*z+data[14]*w,
                       data[3]*x+data[7]*y+data[11]*z+data[15]*w);
  else
  {
    result.x = data[0]*x+data[4]*y+data[8]*z+data[12]*w;
    result.y = data[1]*x+data[5]*y+data[9]*z+data[13]*w;
    result.z = data[2]*x+data[6]*y+data[10]*z+data[14]*w;
    result.w = data[3]*x+data[7]*y+data[11]*z+data[15]*w;
    return result;
  }
}

gewMat4.prototype.determinant = function() 
{
  var m1 = this.data,
  m00 =  m1[0], m01 =  m1[4], m02 =  m1[8],  m03 =  m1[12],
  m10 =  m1[1], m11 =  m1[5], m12 =  m1[9],  m13 =  m1[13],
  m20 =  m1[2], m21 =  m1[6], m22 =  m1[10], m23 = m1[14],
  m30 =  m1[3], m31 =  m1[7], m32 =  m1[11], m33 = m1[15];
  
  return m03 * m12 * m21 * m30-m02 * m13 * m21 * m30-m03 * m11 * m22 * m30+m01 * m13 * m22 * m30+
         m02 * m11 * m23 * m30-m01 * m12 * m23 * m30-m03 * m12 * m20 * m31+m02 * m13 * m20 * m31+
         m03 * m10 * m22 * m31-m00 * m13 * m22 * m31-m02 * m10 * m23 * m31+m00 * m12 * m23 * m31+
         m03 * m11 * m20 * m32-m01 * m13 * m20 * m32-m03 * m10 * m21 * m32+m00 * m13 * m21 * m32+
         m01 * m10 * m23 * m32-m00 * m11 * m23 * m32-m02 * m11 * m20 * m33+m01 * m12 * m20 * m33+
         m02 * m10 * m21 * m33-m00 * m12 * m21 * m33-m01 * m10 * m22 * m33+m00 * m11 * m22 * m33;
}
gewMat4.prototype.inverse = function()
{
  var m1 = this.data,d = this.determinant(),
  m00 =  m1[0], m01 =  m1[4], m02 =  m1[8],  m03 =  m1[12],
  m10 =  m1[1], m11 =  m1[5], m12 =  m1[9],  m13 =  m1[13],
  m20 =  m1[2], m21 =  m1[6], m22 =  m1[10], m23 = m1[14],
  m30 =  m1[3], m31 =  m1[7], m32 =  m1[11], m33 = m1[15];
          
  m1[0] = ( m12*m23*m31 - m13*m22*m31 + m13*m21*m32 - m11*m23*m32 - m12*m21*m33 + m11*m22*m33)/d;
  m1[1] = ( m13*m22*m30 - m12*m23*m30 - m13*m20*m32 + m10*m23*m32 + m12*m20*m33 - m10*m22*m33)/d;
  m1[2] = ( m11*m23*m30 - m13*m21*m30 + m13*m20*m31 - m10*m23*m31 - m11*m20*m33 + m10*m21*m33)/d;
  m1[3] = ( m12*m21*m30 - m11*m22*m30 - m12*m20*m31 + m10*m22*m31 + m11*m20*m32 - m10*m21*m32)/d;
     
  m1[4] = ( m03*m22*m31 - m02*m23*m31 - m03*m21*m32 + m01*m23*m32 + m02*m21*m33 - m01*m22*m33)/d;
  m1[5] = ( m02*m23*m30 - m03*m22*m30 + m03*m20*m32 - m00*m23*m32 - m02*m20*m33 + m00*m22*m33)/d;
  m1[6] = ( m03*m21*m30 - m01*m23*m30 - m03*m20*m31 + m00*m23*m31 + m01*m20*m33 - m00*m21*m33)/d;
  m1[7] = ( m01*m22*m30 - m02*m21*m30 + m02*m20*m31 - m00*m22*m31 - m01*m20*m32 + m00*m21*m32)/d;
     
  m1[8] = ( m02*m13*m31 - m03*m12*m31 + m03*m11*m32 - m01*m13*m32 - m02*m11*m33 + m01*m12*m33)/d;
  m1[9] = ( m03*m12*m30 - m02*m13*m30 - m03*m10*m32 + m00*m13*m32 + m02*m10*m33 - m00*m12*m33)/d;
  m1[10] = ( m01*m13*m30 - m03*m11*m30 + m03*m10*m31 - m00*m13*m31 - m01*m10*m33 + m00*m11*m33)/d;
  m1[11] = ( m02*m11*m30 - m01*m12*m30 - m02*m10*m31 + m00*m12*m31 + m01*m10*m32 - m00*m11*m32)/d;
          
  m1[12] = ( m03*m12*m21 - m02*m13*m21 - m03*m11*m22 + m01*m13*m22 + m02*m11*m23 - m01*m12*m23)/d;
  m1[13] = ( m02*m13*m20 - m03*m12*m20 + m03*m10*m22 - m00*m13*m22 - m02*m10*m23 + m00*m12*m23)/d;
  m1[14] = ( m03*m11*m20 - m01*m13*m20 - m03*m10*m21 + m00*m13*m21 + m01*m10*m23 - m00*m11*m23)/d;
  m1[15] = ( m01*m12*m20 - m02*m11*m20 + m02*m10*m21 - m00*m12*m21 - m01*m10*m22 + m00*m11*m22)/d;
  
  return this;
}
// Extract axis and angle from a rotate matrix, outTheta in radian
// Still missing some special case!!
gewMat4.prototype.extractRotateAxis = function(outAxis)
{
  var data = this.data;
  var zs = (data[1]-data[4])/2;
  var ys = (data[8]-data[2])/2;
  var xs = (data[6]-data[9])/2;
  
  var is_xs_equal_zero = gewIsEqual(xs, 0);
  var is_ys_equal_zero = gewIsEqual(ys, 0);
  var is_zs_equal_zero = gewIsEqual(zs, 0);
  if( is_xs_equal_zero &&
      is_ys_equal_zero &&
      is_zs_equal_zero )
  {    
    outAxis.set(0,0,0);
    return 0;
  }
  else if(is_xs_equal_zero)
  {
    var x = 0;
    var c = data[0];
    var z = Math.sqrt((data[10] - c)/(1-c));
    if(z!=0)
      var s = zs/z;      
    var y = Math.sqrt((data[5] - c)/(1-c));
    if(y!=0)
      var s = ys/y;
  }
  else if(is_ys_equal_zero)
  {
    var y = 0;
    var c = data[5];
    var z = Math.sqrt((data[10] - c)/(1-c));
    if(z!=0)
      var s = zs/z;      
    var x = Math.sqrt((data[0] - c)/(1-c));
    if(x!=0)
      var s = xs/x;
  }
  else if(is_zs_equal_zero)
  {
    var z = 0;
    var c = data[10];
    var x = Math.sqrt((data[0] - c)/(1-c));
    if(x!=0)
      var s = xs/x;      
    var y = Math.sqrt((data[5] - c)/(1-c));
    if(y!=0)
      var s = ys/y; 
  }
  else 
  {
    var y_div_x = ys / xs;
    var z_div_x = zs / xs;
    
    var x = Math.sqrt(1 / (1 + y_div_x * y_div_x + z_div_x * z_div_x));
    var y = y_div_x * x;
    var z = z_div_x * x;
    var s = xs / x;
    var f0 = data[0] - x * x;
    var f1 = 1 - x * x;
    if (gewIsEqual(f1, 0)) {
      f0 = data[5] - y * y;
      f1 = 1 - y * y;
      if (gewIsEqual(f1, 0)) {
        f0 = data[10] - z * z;
        f1 = 1 - z * z;
      }
    }
    var c = f0 / f1;
  } 
  
  outAxis.set(x, y, z);
  var theta = Math.acos(c);
  
  if(s<0)
    theta = -theta;
  return theta;
}
// STATIC FUNCTION 
// Return rotate matrix, theta in radian
gewMat4.getRotate = function(axis,theta,result)
{
  axis.normalize();
  var c = Math.cos(theta);
  var s = Math.sin(theta);
  var d  = 1-c;
  
  var x = axis.x;
  var y = axis.y;
  var z = axis.z;
  if(!result)
    return new gewMat4( [x*x*d+c, y*x*d+z*s, x*z*d-y*s, 0, x*y*d-z*s, y*y*d+c, y*z*d+x*s, 0, x*z*d+y*s, y*z*d-x*s,z*z*d+c,0,0,0,0,1]);
  else
  {
    result.set([x*x*d+c, y*x*d+z*s, x*z*d-y*s, 0, x*y*d-z*s, y*y*d+c, y*z*d+x*s, 0, x*z*d+y*s, y*z*d-x*s,z*z*d+c,0,0,0,0,1]);
    return result;
  }
}
gewMat4.getRotateXYZ = function(x,y,z,theta,result)
{
  //axis.normalize();
  var c = Math.cos(theta);
  var s = Math.sin(theta);
  var d  = 1-c;  
  if(!result)
    return new gewMat4( [x*x*d+c, y*x*d+z*s, x*z*d-y*s, 0, x*y*d-z*s, y*y*d+c, y*z*d+x*s, 0, x*z*d+y*s, y*z*d-x*s,z*z*d+c,0,0,0,0,1]);
  else
  {
    result.set([x*x*d+c, y*x*d+z*s, x*z*d-y*s, 0, x*y*d-z*s, y*y*d+c, y*z*d+x*s, 0, x*z*d+y*s, y*z*d-x*s,z*z*d+c,0,0,0,0,1]);
    return result;
  }
}
// Return translate matrix
gewMat4.getTranslate = function(vector,result)
{
  if(!result)
    return new gewMat4([1,0,0,0, 0,1,0,0, 0,0,1,0, vector.x, vector.y, vector.z, 1]);
  else
  {
    result.set([1,0,0,0, 0,1,0,0, 0,0,1,0, vector.x, vector.y, vector.z, 1]);
    return result;
  }
}
gewMat4.getTranslateXYZ = function(x,y,z,result)
{
  if(!result)
    return new gewMat4([1,0,0,0, 0,1,0,0, 0,0,1,0, x, y, z, 1]);
  else
  {
    result.set([1,0,0,0, 0,1,0,0, 0,0,1,0, x, y, z, 1]);
    return result;
  }
}

// Return scale matrix
gewMat4.getScale = function(vector,result)
{
  if(!result)
    return new gewMat4([vector.x,0,0,0, 0,vector.y,0,0, 0,0,vector.z,0, 0,0,0,1]);
  else
  {
    result.set([vector.x,0,0,0, 0,vector.y,0,0, 0,0,vector.z,0, 0,0,0,1]);
    return result;
  }  
}
gewMat4.getScaleXYZ = function(x,y,z,result)
{
  if(!result)
    return new gewMat4([x,0,0,0, 0,y,0,0, 0,0,z,0, 0,0,0,1]);
  else
  {
    result.set([x,0,0,0, 0,y,0,0, 0,0,z,0, 0,0,0,1]);
    return result;
  }  
}